﻿/**
 * MegaCursor Class
 * Copyright (c) 2009 designmarco
 * @author Marco Di Giuseppe  |  http://designmarco.com
 * marco[at]designmarco[dot]com
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
package com.designmarco.utils
{
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.display.DisplayObject;
	import flash.display.InteractiveObject;
	import flash.events.MouseEvent;
	import flash.events.Event;
	import flash.ui.Mouse;
	import flash.geom.Point;
	import flash.utils.Dictionary;
	/**
	 * MegaCursor is class that manages all custom cursor activity. <br/><br/>
	 *
	 * <strong>Features:</strong> <br/>
	 * <ul>
	 * <li>Cursor remains at the top most level. </li>
	 * <li>Visibility toggled when mouse leaves stage area.  </li>
	 * <li>Display different cursors with multiple targets.  </li>
	 * <li>Set any displayobject property on the cursor.   </li>
	 * </ul>
	 *
	 *@example
	 *Call the init and pass a stage reference to activate.
	 *<code>
	 *		MegaCursor.init(stage);
	 * </code>
	 *This will display a custom cursor rotated 90 degress.
	 *<code>
	 *		MegaCursor.manage(cursor, {rotation:90});
	 *</code>
	 *This will map a custom cursor to a movieclip on the stage.
	 *<code>
	 * 		MegaCursor.setMap(cursor, mc);
	 *</code>
	 *If you like to offset the x and y by 10 pixels use the props object:
	 *<code>
	 *		MegaCursor.manage(cursor, {x:10, y:10});
	 *</code>
	 *
	 */
	public class MegaCursor
	{
		/** @private primary cursor that covers the entire stage **/
		private static var mainCursor:DisplayObject;
		
		/** @private current active cursor reference **/
		private static var _cursor:DisplayObject;
		
		/** @private stage reference **/
		private static var _stage:Stage;
		
		/** @private event listening status **/
		private static var listening:Boolean;
		
		/** @private stores cursor and trigger object pairs **/
		private static var cursorMap:Dictionary;
		
		/** @private container holding cursor **/
		private static var holder:Sprite;
		
		/** @private if custom cursor was initialized **/
		private static var hasInit:Boolean;
		
		/**
		 * Initialize MegaCursor
		 * The init must be called to start managing cursors.
		 * @param stage reference that provides stage access
		 */
		public static function init(stage:Stage = null):void
		{
			if (hasInit || !stage) return;
			
			hasInit 	= true;
			_stage  	= stage;
			cursorMap 	= new Dictionary(false);
			holder  	= new Sprite();
			holder.mouseEnabled  = false;
			holder.tabEnabled 	 = false;
			holder.mouseChildren = false;
		}
		
		/**
		 * Manage a Custom Cursor Object
		 * @param	cursor	displayobject representing custom cursor
		 * @param	props   cursor display properties
		 */
		public static function show( stage:Stage, cursor:DisplayObject, props:Object = null ):void
		{
			if (!hasInit) init(stage);
			mainCursor = cursor;
			if(props) setProps(mainCursor, props);
			showCursor(mainCursor);
		}
		
		
		/**
		 * Display Custom Cursor
		 * @param	cursor	displayobject representing custom cursor
		 */
		private static function showCursor(cursor:DisplayObject):void
		{
			if (!hasInit || !_stage) return;
			
			Mouse.hide();
			if (holder.numChildren) clearHolder();
			_cursor = cursor;
			holder.addChild(_cursor);
			setListeners(true);
			updateDepth();
		}

		/**
		 * Listen for the Event.ADDED Notifications from the stage
		 * so we can always keep the cursor on top of everything else.
		 * @param	event	Event.ADDED
		 */
		private static function updateDepth(event:Event = null):void
		{
			_stage.addChild(holder);
			onMouseMove();
		}
		
		/**
		 * Remove Custom Cursor
		 */
		public static function clearCursor():void
		{
			if (!hasInit) return;
			Mouse.show();
			setListeners(false);
			clearHolder();
		}

		/**
		 * Set target object to activate custom cursor during interaction
		 * @param	cursor  displayobject that replaces the system cursor
		 * @param   target	 object triggering custom cursor
		 * @param	props	 cursor displayproperties
		 */
		public static function setMap( cursor:DisplayObject, target:InteractiveObject, props:Object = null):void
		{
			if (!hasInit) return;
			if(props) setProps(cursor, props);
			cursorMap[target] = cursor;
			target.addEventListener(MouseEvent.ROLL_OVER, mapHandler, false, 0, true);
			target.addEventListener(MouseEvent.ROLL_OUT, mapHandler, false, 0, true);
		}

		/**
		 * Remove Cursor Target Map
		 * @param target 	object activating custom cursor
		 */
		public static function clearMap(target:InteractiveObject):void
		{
			if (target.hasEventListener(MouseEvent.ROLL_OVER))
			{
				target.removeEventListener(MouseEvent.ROLL_OVER, mapHandler);
				target.removeEventListener(MouseEvent.ROLL_OUT, mapHandler);
				cursorMap[target] = null;
			}
		}
		
		/**
		 * Remove all Cursor Maps
		 */
		public static function clearAllMaps():void
		{
			for (var target:Object in cursorMap)
			{
				target.removeEventListener(MouseEvent.MOUSE_OVER, mapHandler);
				target.removeEventListener(MouseEvent.MOUSE_OUT, mapHandler);
				cursorMap[target] = null;
			}
		}

		/**
		 * Set Cursor Properties
		 * @param	cursor	displayobject replacing system cursor
		 * @param	props	set any displayobject related property
		 */
		public static function setProps(cursor:DisplayObject, props:Object):void
		{
			for (var key:Object in props)
			{
				cursor[key] = props[key];
			}
		}
		
		/**
		 * Clean-up all references
		 */
		public static function destroy():void
		{
			if (!hasInit) return;
			
			clearCursor();
			clearAllMaps();
			if (mainCursor)  mainCursor = null;
			if (_cursor) 	 _cursor 	= null;
			_stage 	= null;
			holder 	= null;
			hasInit = false;
		}
		
		/**
		 * Sync Custom Cursor with the Mouse
		 * @param 	event	MouseEvent.MOUSE_MOVE
		 */
		private static function onMouseMove(event:MouseEvent = null):void
		{
			if (!_cursor.visible)
				_cursor.visible = true;
				
			holder.x = _stage.mouseX;
			holder.y = _stage.mouseY;

			if (event)
				event.updateAfterEvent();
		}

		/**
		 * Hide Cursor when the Mouse leaves the stage.
		 * @param	event	Event.MOUSE_LEAVE
		 */
		private static function onMouseLeave(event:Event):void
		{
			Mouse.show();
			_cursor.visible = false;
			_stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseReturn, false, 0, true);
		}

		/**
		 * Show Custom Cursor when returning to the stage
		 * @param	event	MouseEvent.MOUSE_MOVE
		 */
		private static function onMouseReturn(event:Event):void
		{
			Mouse.hide();
			_cursor.visible = true;
			_stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseReturn);
		}

		/**
		 * Manage Event Listeners for Custom Cursor
		 * @param	b	true to add | false to remove
		 */
		private static function setListeners(b:Boolean):void
		{
			if (listening == b) return;
			listening = b;
			var action:String = (b) ? "addEventListener" : "removeEventListener";
			_stage[action](MouseEvent.MOUSE_MOVE, onMouseMove);
			_stage[action](Event.MOUSE_LEAVE, onMouseLeave);
			_stage[action](Event.ADDED, updateDepth);
		}

		/**
		 * Display Custom Cursor when over trigger Object
		 * @param	event MouseEvent.ROLL_OVER and MouseEvent.ROLL_OUT
		 */
		private static function mapHandler(event:MouseEvent):void
		{
			switch(event.type)
			{
				case MouseEvent.ROLL_OVER:
					var cursor:DisplayObject = cursorMap[DisplayObject(event.target)];
					if (cursor) showCursor(cursor);
					break;
					
				case MouseEvent.ROLL_OUT:
					if (mainCursor)	showCursor(mainCursor);
					else clearCursor();
					break;
					
				default:
					break;
			}
		}

		/**
		 * Remove all children of holder from the Display List
		 */
		private static function clearHolder():void
		{
			while (holder.numChildren)
			{
				holder.removeChildAt(0);
			}
		}
	}
}